package backend.entry;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Vector;

import javax.swing.Timer;



import backend.Algoritmos.Simulator;
import backend.Algoritmos.Solver;
import backend.Utiles.DataFactory;
import backend.estructuras.common.Almacen;
import backend.estructuras.common.Conexion;
import backend.estructuras.common.Paquete;
import backend.estructuras.common.Ruta;
import backend.estructuras.common.Vuelo;
import backend.walter.WSolver;

import com.dp.client.dto.AlmacenSimulationDTO;
import com.dp.client.dto.ConexionSimulationDTO;
import com.dp.client.dto.Fecha;
import com.dp.client.dto.ResultadosQuerySimulacionDTO;
import com.dp.server.backend.PaqueteBackend;
import com.dp.server.backend.RutaDeVueloBackend;
import com.dp.server.backend.VueloBackend;
import com.google.gson.Gson;



public class RequestHandler implements Runnable {
	private Socket socket;
	public static final String PAQUETES_PREFIJO = "paquetes";
	public static final String GENERA_RUTAS = "genera_rutas";
	public static final String SELECCIONA = "selecciona";
	public static final String SIMULACION_PREFIJO = "simulacion";
	public static final String SIMULA_SIMPLE = "simula_simple";
	public static final String CAPACIDAD_ALMACEN = "capacidad_almacen";
	public static final String ESPACIO_LLENO_ALMACEN = "espacio_lleno_almacen";
	public static final String CAPACIDAD_DISPONIBLE_ALMACEN = "capacidad_disponible_almacen";
	public static final String START = "start";
	public static final String RESTART = "restart";
	public static final String CREAR_SESION_SIMULACION = "crear_sesion";
	public static final String START_SIMULACION = "iniciar_simulacion";
	public static final String CAMBIO_CAPACIDAD_CONEXION = "cambiar_capacidad_conexion";
	public static final String CAMBIO_CAPACIDAD_ALMACEN = "cambiar_capacidad_almacen";
	public static final String GET_DATOS_WINDOW_SIMULACION = "get_datos_simulacion_para_intervalo_de_tiempo";
	
	public RequestHandler(Socket socket){
		this.socket = socket;
	}
	@Override
	public void run() {
		try {
			
			Mensaje mensaje = getMensajeFromCliente();
			mensaje.prepareIterator();
			String command = mensaje.nextLine();
			System.out.println("Comando: " + mensaje);
			if(command != null){
				if(command.equals(PAQUETES_PREFIJO)){
					gestionaComandosPaquetes(mensaje);
				}else if(command.equals(SIMULACION_PREFIJO)){
					gestionaComandosSimulacion(mensaje);
				}else if(command.equals(START)){
					comienzaSiMuerto();
				}else if(command.equals(RESTART)){
					mata();
					comienzaSiMuerto();
				}
			}
			socket.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			
		}
	}
	static void mata(){
		solver = null;
	}
	private synchronized void gestionaComandosPaquetes(Mensaje mensaje) throws IOException {
		System.out.println("Entra a comandos de paquete"); System.out.flush();
		String command = mensaje.nextLine();
		comienzaSiMuerto();
		Gson gson = new Gson();
		InputStream is = socket.getInputStream();
		Scanner in = new Scanner(is);
		OutputStream os = socket.getOutputStream();
		PrintWriter pw = new PrintWriter(os, true);
		
		if(command.equals(GENERA_RUTAS)){
			//lee paquete
			String paquetejson = mensaje.nextLine();
			System.out.println(paquetejson);
			PaqueteBackend paqueteBackend = gson.fromJson(paquetejson, PaqueteBackend.class);
			Paquete paquete = new Paquete(solver.getAlmacenPorNombre(paqueteBackend.almacenOrigenNombre),
										  solver.getAlmacenPorNombre(paqueteBackend.almacenDestinoNombre),
										  paqueteBackend.salida, true);
		
			//devuelve rutas
			RutaDeVueloBackend[] result;
			result = solver.CalculaRutasParaPaquete(paquete);
			ImprimeLinea(pw, gson.toJson(result));
		}else if(command.equals(SELECCIONA)){
			//selecciona ruta y devuelve true si pudo usarla
			String paquetejson = mensaje.nextLine();
			PaqueteBackend paqueteBackend = gson.fromJson(paquetejson, PaqueteBackend.class);
			String rutajson = mensaje.nextLine();
			RutaDeVueloBackend ruta = gson.fromJson(rutajson, RutaDeVueloBackend.class);
			List<Vuelo> vuelos = new ArrayList<Vuelo>();
			for(VueloBackend v : ruta.vuelos){
				int idVuelo = v.idVuelo;
				vuelos.add(solver.getVueloById(idVuelo));
			}
			Paquete paquete = new Paquete(solver.getAlmacenPorNombre(paqueteBackend.almacenOrigenNombre),
										  solver.getAlmacenPorNombre(paqueteBackend.almacenDestinoNombre),
										  paqueteBackend.salida, true);
			boolean result = solver.EjecutaRutaDePaquete(paquete, new Ruta(vuelos, paquete));
			pw.print(result); pw.flush();
		}else if(command.equals(CAPACIDAD_ALMACEN)){
			Fecha fecha = gson.fromJson(mensaje.nextLine(), Fecha.class);
			Almacen almacen = solver.getAlmacenPorNombre(mensaje.nextLine());
			int result = solver.getCapacidadDeAlmacenEnTiempoDado(almacen, fecha);
			pw.println(result); pw.flush();
		}else if(command.startsWith(ESPACIO_LLENO_ALMACEN)){
			Fecha fecha = gson.fromJson(mensaje.nextLine(), Fecha.class);
			Almacen almacen = solver.getAlmacenPorNombre(mensaje.nextLine());
			int result = solver.getEspacioLlenoDeAlmacenEnTiempoDado(almacen, fecha);
			pw.println(result); pw.flush();
		}else if(command.equals(CAPACIDAD_DISPONIBLE_ALMACEN)){
			Fecha fecha = gson.fromJson(mensaje.nextLine(), Fecha.class);
			Almacen almacen = solver.getAlmacenPorNombre(mensaje.nextLine());
			int result = solver.getBottleneckDeAlmacenEnTiempoDado(almacen, fecha);
			pw.println(result); pw.flush();
		}
		in.close();
		pw.close();		
	}
	private void ImprimeLinea(PrintWriter pw, String linea) {
		pw.println(linea); pw.flush();
	}
	
	final static Map<Integer, Simulator> simulaciones = new Hashtable<>();
	
	private synchronized void gestionaComandosSimulacion(Mensaje mensaje) throws IOException{
		String command = mensaje.nextLine();
		Gson gson = new Gson();
		OutputStream os = socket.getOutputStream();
		PrintWriter pw = new PrintWriter(os, true);
		
		if(command.equals(SIMULA_SIMPLE)){
			pw.println(getResultFromSimulator()); pw.flush();
		}else if(command.equals(CREAR_SESION_SIMULACION)){
			int tiempoMinutos = Integer.parseInt(mensaje.nextLine());
			int dias = Integer.parseInt(mensaje.nextLine());
			
			Simulator sim = new Simulator(new WSolver());
			sim.setDias(dias);
			sim.cargarDatosDesdeBD();
			final int id = simulaciones.size();
			simulaciones.put(id, sim);
			new Timer(tiempoMinutos * 60 * 1000, new ActionListener() {
				
				@Override
				public void actionPerformed(ActionEvent e) {
					if(simulaciones.containsKey(id))
						simulaciones.remove(id);
				}
			});
			Mensaje m = new Mensaje();
			m.addLinea("" + id);
			pw.println(gson.toJson(m, Mensaje.class)); pw.flush();
		}else if(command.equals(GET_DATOS_WINDOW_SIMULACION)){
			int id = Integer.parseInt(mensaje.nextLine());
			Fecha inicio = gson.fromJson(mensaje.nextLine(), Fecha.class);
			Fecha fin = gson.fromJson(mensaje.nextLine(), Fecha.class);
			
			Simulator sim = simulaciones.get(id);
			//almacenes
			HashMap<Integer, Integer> mayorUsoAlmacen = new HashMap<>();
			HashMap<Integer, Fecha> mayorUsoAlmacenFecha = new HashMap<>();
			
			for(Almacen a : sim.getSolver().getAlmacenes()){
				Fecha cuando = new Fecha(0);
				int bot = a.getBottleNeckParaIntervaloDeTiempo(inicio, fin, cuando);
				mayorUsoAlmacen.put(a.getId(), a.getCapacidadEnInstanteDeTiempo(inicio) - bot);
				mayorUsoAlmacenFecha.put(a.getId(), cuando);
			}
			//conexiones
			HashMap<Integer, Integer> mayorUsoConexion = new HashMap<>();
			HashMap<Integer, Fecha> mayorUsoConexionFecha = new HashMap<>();
			
			for(Conexion c : sim.getSolver().getConexiones()){
				mayorUsoConexion.put(c.getId(), 0);
				mayorUsoAlmacenFecha.put(c.getId(), new Fecha());
			}
			for(Vuelo v : sim.getSolver().getVuelos())
				if(v.getFechaSalida().compareTo(inicio) >= 0 && v.getFechaLlegada().compareTo(fin) < 0){
					int actual = mayorUsoConexion.get(v.getConexion().getId());
					Fecha cuando = v.getFechaSalida();
					int nuevo = v.getConexion().getCapacidadEnInstanteDeTiempo(v.getFechaSalida()) - v.getEspacioRestante();
					if(actual < nuevo){
						mayorUsoConexion.put(v.getConexion().getId(), nuevo);
						mayorUsoAlmacenFecha.put(v.getConexion().getId(), cuando);
					}
				}
			
			//almacenes
			List<AlmacenSimulationDTO> almacenesResult = new ArrayList<>();
			for(Almacen a : sim.getSolver().getAlmacenes()){
				almacenesResult.add(new AlmacenSimulationDTO(a.getId(),a.getNombre(), a.getLatitud(), a.getLongitud(), 
						mayorUsoAlmacen.get(a.getId()), a.getCapacidadEnInstanteDeTiempo(new Fecha()), mayorUsoAlmacenFecha.get(a.getId())));
			}
			//conexiones
			List<ConexionSimulationDTO> conexionesResult = new ArrayList<>();
			for(Conexion c : sim.getSolver().getConexiones()){
				conexionesResult.add(new ConexionSimulationDTO(c.getOrigen().getId(), c.getDestino().getId(), mayorUsoConexion.get(c.getId()), 
						c.getCapacidadEnInstanteDeTiempo(new Fecha()), mayorUsoConexionFecha.get(c.getId())));
			}
			ResultadosQuerySimulacionDTO results = new ResultadosQuerySimulacionDTO(almacenesResult, conexionesResult);
			pw.println(gson.toJson(results, ResultadosQuerySimulacionDTO.class)); pw.flush();
			
		}else if(command.equals(START_SIMULACION)){
			int id = Integer.parseInt(mensaje.nextLine());
			Simulator sim = simulaciones.get(id);
			sim.run();
			Mensaje output = new Mensaje();
			//output.addLinea(sim.runEstadisticas());
			output.addLinea(true);
			pw.println(gson.toJson(output, Mensaje.class)); pw.flush();
		}else if(command.equals(CAMBIO_CAPACIDAD_CONEXION)){
			int id = Integer.parseInt(mensaje.nextLine());
			Simulator sim = simulaciones.get(id);
			int idConexion = Integer.parseInt(mensaje.nextLine());
			int nuevaCapacidad = Integer.parseInt(mensaje.nextLine());
			//Fecha fechaCambio = gson.fromJson(mensaje.nextLine(), Fecha.class);
			Conexion c = sim.getSolver().getConexionPorId(idConexion);
			//c.addCambioDeCapacidad(nuevaCapacidad, fechaCambio);
			c.setCapacidadEnFechaCero(nuevaCapacidad);
			Mensaje output = new Mensaje();
			output.addLinea("" + true);
			pw.println(gson.toJson(output, Mensaje.class)); pw.flush();
		}else if(command.equals(CAMBIO_CAPACIDAD_ALMACEN)){
			int id = Integer.parseInt(mensaje.nextLine());
			Simulator sim = simulaciones.get(id);
			int idAlmacen = Integer.parseInt(mensaje.nextLine());
			int nuevaCapacidad = Integer.parseInt(mensaje.nextLine());
			//Fecha fechaCambio = gson.fromJson(mensaje.nextLine(), Fecha.class);
			Almacen c = sim.getSolver().getAlmacenPorId(idAlmacen);
			//c.addCambioDeCapacidad(nuevaCapacidad, fechaCambio);
			c.setCapacidadEnFechaCero(nuevaCapacidad);
			Mensaje output = new Mensaje();
			output.addLinea("" + true);
			pw.println(gson.toJson(output, Mensaje.class)); pw.flush();
		}
		pw.close();		
	
	}
	private synchronized void comienzaSiMuerto() {
		if(solver == null){
			solver = new WSolver();
			solver.cargarDatosDesdeBD(false);
		}
	}
	
	private Mensaje getMensajeFromCliente() throws IOException {
		Scanner in = new Scanner(socket.getInputStream());
		Gson gson = new Gson();
		Mensaje m = gson.fromJson(in.nextLine(), Mensaje.class);
		return m;
	}
	private String getResultFromSimulator() {
		Simulator simulator = new Simulator(new WSolver());
		String output = "";
		
			InputStream inputStream;
			long start = System.currentTimeMillis();
				simulator.setDias(365);
				simulator.run();
				output += simulator.runEstadisticas();
			
			long end = System.currentTimeMillis();
			return output + "\n" + "tiempo en segs.: " + (end - start) / 1000.0;
		
	}
	static Solver solver = null;
}
